﻿//SecurityChangeContainer.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Storages
{
	using System;
	using System.Collections.Generic;
	using System.Linq;

	using Ecng.Collections;
	using Ecng.Common;

	using StockSharp.BusinessEntities;

	using Wintellect.PowerCollections;

	/// <summary>
	/// Контейнер для накопления изменений по инструментам для последующего доступа к изменениям как к историческим данным.
	/// </summary>
	public class SecurityChangeContainer
	{
		/// <summary>
		/// Контейнер для конкретного инструмента.
		/// </summary>
		public class SecurityInfo
		{
			private readonly Security _security;
			private readonly object _syncRoot = new object();
			private readonly OrderedDictionary<DateTime, Dictionary<SecurityChangeTypes, SecurityChange>> _changes =
				new OrderedDictionary<DateTime, Dictionary<SecurityChangeTypes, SecurityChange>>((d1, d2) => -d1.CompareTo(d2));

			internal SecurityInfo(Security security)
			{
				if (security == null)
					throw new ArgumentNullException("security");

				_security = security;
				Types = Enumerator.JoinMask<SecurityChangeTypes>();
			}

			/// <summary>
			/// Получить или устрановить тип отслеживаемых изменения для инструмента.
			/// </summary>
			public SecurityChangeTypes Types { get; set; }

			/// <summary>
			/// Получить количество всех хранящихся изменений.
			/// </summary>
			/// <returns>Количество хранящихся изменений.</returns>
			public int GetCount()
			{
				lock (_syncRoot)
					return _changes.Count;
			}

			/// <summary>
			/// Получить количество хранящихся изменений типа <paramref name="type"/>.
			/// </summary>
			/// <param name="type">Тип изменений.</param>
			/// <returns>Количество хранящихся изменений.</returns>
			public int GetCount(SecurityChangeTypes type)
			{
				lock (_syncRoot)
					return _changes.Sum(p => p.Value.ContainsKey(type) ? 1 : 0);
			}

			/// <summary>
			/// Получить изменения по индексу.
			/// </summary>
			/// <param name="index">Индекс. Ноль означает последние изменения.</param>
			/// <returns>Найденные изменения. Если для переданного индекса изменений нет, то будет возвращено пустое множество.</returns>
			public IDictionary<SecurityChangeTypes, SecurityChange> GetChanges(int index)
			{
				lock (_syncRoot)
				{
					if (_changes.Count > index)
						return _changes[_changes.Keys.ElementAt(index)].ToDictionary();
				}

				return new Dictionary<SecurityChangeTypes, SecurityChange>();
			}

			/// <summary>
			/// Получить изменения, произошедшие раньше текущего времени минус временное смещение <paramref name="offset"/>.
			/// </summary>
			/// <param name="offset">Временное смещение от текущего времени.</param>
			/// <returns>Найденные изменения. Если изменения присутствуют только для более раннего периода, то будет возвращено пустое множество.</returns>
			public IDictionary<SecurityChangeTypes, SecurityChange> GetChanges(TimeSpan offset)
			{
				return GetChanges(_security.GetMarketTime() - offset);
			}

			/// <summary>
			/// Получить изменения, произошедшие до <paramref name="from"/>.
			/// </summary>
			/// <param name="from">Временная метка.</param>
			/// <returns>Найденные изменения. Если изменения присутствуют только для более раннего периода, то будет возвращено пустое множество.</returns>
			public IDictionary<SecurityChangeTypes, SecurityChange> GetChanges(DateTime from)
			{
				return GetChanges(from, DateTime.MinValue);
			}

			/// <summary>
			/// Получить изменения, произошедшие до <paramref name="from"/> и после <paramref name="to"/>.
			/// </summary>
			/// <param name="from">Временная метка начала.</param>
			/// <param name="to">Временная метка окончания.</param>
			/// <returns>Найденные изменения. Если изменения присутствуют только для более раннего или более позднего периода, то будет возвращено пустое множество.</returns>
			public IDictionary<SecurityChangeTypes, SecurityChange> GetChanges(DateTime from, DateTime to)
			{
				var changes = new Dictionary<SecurityChangeTypes, SecurityChange>();

				lock (_syncRoot)
				{
					var view = _changes.Range(from, true, to, true);

					foreach (var dict in view.Values)
					{
						foreach (var pair in dict)
						{
							if (!changes.ContainsKey(pair.Key))
								changes.Add(pair.Key, pair.Value);
						}
					}
				}

				return changes;
			}

			internal DateTime? ProcessSecurity()
			{
				var changes = _security.GetNewChanges(Types);

				var first = changes.FirstOrDefault();

				if (first == null)
					return null;

				lock (_syncRoot)
				{
					var dict = GetChanges(first.Time);

					foreach (var change in changes)
						dict.Add(change.Type, change);
				}

				return first.Time;
			}

			private Dictionary<SecurityChangeTypes, SecurityChange> GetChangesDict(DateTime time)
			{
				// mika
				// так как в один момент времени инструмент может иметь только одно состояние, то ситуация, когда для одного времени
				// инструмент имеет два и более состояний, возможно только в случае некачественного фида (недостаточная дискретность времени).
				// раз он льет неправильно данные, то мы пытаемся хоть как-то их исправить (делая сдвиг на 1 тик).
				while (true)
				{
					var dict = _changes.TryGetValue(time);

					if (dict == null)
					{
						dict = new Dictionary<SecurityChangeTypes, SecurityChange>();
						_changes.Add(time, dict);
						return dict;
					}

					time = time.AddTicks(1);
				}
			}

			internal void Recycle(DateTime firstChangeTime)
			{
				lock (_syncRoot)
					_changes.RemoveMany(_changes.Keys.Where(k => k < firstChangeTime).ToArray());
			}
		}

		private readonly SynchronizedDictionary<Security, SecurityInfo> _info = new SynchronizedDictionary<Security, SecurityInfo>();
		private TimeSpan _maxChangesKeepTime;

		/// <summary>
		/// Создать <see cref="SecurityChangeContainer"/>.
		/// </summary>
		public SecurityChangeContainer()
		{
		}

		private TimeSpan _changesKeepTime = TimeSpan.FromDays(1);

		/// <summary>
		/// Время хранения изменений в памяти. По-умолчанию равно 1-му дню.
		/// </summary>
		/// <remarks>Если значение установлено в <see cref="TimeSpan.Zero"/>, то изменения не будут удаляться.</remarks>
		public TimeSpan ChangesKeepTime
		{
			get { return _changesKeepTime; }
			set
			{
				if (value < TimeSpan.Zero)
					throw new ArgumentOutOfRangeException("value", value, "Время хранения изменений не может быть отрицательным.");

				_changesKeepTime = value;
				_maxChangesKeepTime = TimeSpan.FromTicks((long)(value.Ticks * 1.5));
				RecycleChanges();
			}
		}

		/// <summary>
		/// Получить контейнер для конкретного инструмента.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <returns>Контейнер.</returns>
		public SecurityInfo GetInfo(Security security)
		{
			return _info.SafeAdd(security, key => new SecurityInfo(key));
		}

		/// <summary>
		/// Обработать измененные инструменты.
		/// </summary>
		/// <param name="securities">Измененные инструменты.</param>
		public void ProcessSecurities(IEnumerable<Security> securities)
		{
			if (securities == null)
				throw new ArgumentNullException("securities");

			foreach (var security in securities)
			{
				var time = GetInfo(security).ProcessSecurity();

				if (time == null)
					return;

				_lastChangeTime = (DateTime)time;

				if (_firstChangeTime == default(DateTime))
					_firstChangeTime = _lastChangeTime;
			}

			RecycleChanges();
		}

		private DateTime _firstChangeTime;
		private DateTime _lastChangeTime;

		private void RecycleChanges()
		{
			if (ChangesKeepTime == TimeSpan.Zero)
				return;

			var diff = _lastChangeTime - _firstChangeTime;

			if (diff <= _maxChangesKeepTime)
				return;

			_firstChangeTime = _lastChangeTime - ChangesKeepTime;

			foreach (var info in _info.SyncGet(d => d.Values.ToArray()))
			{
				info.Recycle(_firstChangeTime);
			}
		}
	}
}